import hashlib
import pyaes
import blowfish
import binascii
from urllib.request import urlopen

import deezer.utils
from generic.track import Track
import deezer.artist
import deezer.album
import deezer.track_quality

class DeezerTrack(Track):
  def set_raw_data(self, song_data):
    self.set_values(
      title = song_data["SNG_TITLE"],
      song_id = song_data["SNG_ID"],
      artists = list(map(
        lambda a: deezer.artist.DeezerArtist.grab_or_create(
          a["ART_ID"],
          artist_id = a["ART_ID"],
          name = a["ART_NAME"],
          picture_url = deezer.utils.ARTIST_PICTURES_URL % a["ART_PICTURE"]
        ),
        song_data["ARTISTS"]
      )),
      album = deezer.album.DeezerAlbum.grab_or_create(
        song_data["ALB_ID"],
        album_id = song_data["ALB_ID"],
        title = song_data["ALB_TITLE"],
        picture_url = deezer.utils.ALBUM_PICTURES_URL % song_data["ALB_PICTURE"]
      ),
      md5_origin = song_data["MD5_ORIGIN"],
      media_version = song_data["MEDIA_VERSION"],
      qualities = deezer.track_quality.DeezerTrackQuality.from_raw_data(song_data),
      is_full = True
    )

  def set_values(
    self,
    song_id = None,
    title = None,
    artists = None,
    album = None,
    md5_origin = None,
    media_version = None,
    qualities = None,
    is_full = False, # if the album is fully loaded
    raw_data = None
  ):
    if raw_data != None:
      self.set_raw_data(raw_data)
      return
    self.song_id = song_id
    self.title = title
    self.artists = artists
    self.album = album
    self.md5_origin = md5_origin
    self.media_version = media_version
    self.qualities = qualities
    self.is_full = is_full

  def get_full_data(self, deezer_instance):
    if not self.is_full:
      raw_data = deezer_instance.get_track_data(self.song_id)
      self.set_raw_data(raw_data)
    if self.artists != None:
      for artist in self.artists:
        if not artist.is_full:
          artist.get_full_data(deezer_instance)
    if self.album != None:
      if not album.is_full:
        self.album.get_full_data(deezer_instance)

  def stream(self, stream, quality = None, progress_callback = None):
    if quality == None:
      quality = self.guess_quality()
    file_format = deezer.track_quality.DeezerTrackQuality.get_file_format(quality)

    # get track url
    #  - step 1: put a bunch of info together and hash it
    step1 = b'\xa4'.join(map(
      lambda s: s.encode(), [
        self.md5_origin,
        file_format,
        str(self.song_id),
        self.media_version
      ]
    ))
    hash = hashlib.new("md5")
    hash.update(step1)
    step1_hash = hash.hexdigest()

    #  - step 2: hash + info + add padding
    step2 = str.encode(step1_hash) + b'\xa4' + step1 + b'\xa4'
    while len(step2) % 16 > 0:
      step2 += b'.'

    #  - step 3: AES encryption to get url
    # it will encrypt in parts of 16
    step3 = b''
    aes = pyaes.AESModeOfOperationECB(b'jo6aey6haid2Teih')
    for index in range(int(len(step2) / 16)):
      block = step2[(index * 16):(index * 16) + 16]
      step3 += binascii.hexlify(aes.encrypt(block))

    #  - step 4: make url
    url = "http://e-cdn-proxy-{}.deezer.com/mobile/1/{}".format(
      self.md5_origin[0], # first char of md5origin is cdn to use
      step3.decode('utf-8')
    )

    # get blowfish key
    hash = hashlib.new("md5")
    hash.update(str(self.song_id).encode())
    hash = hash.hexdigest()
    key = ""
    for i in range(16):
      key += chr(ord(hash[i]) ^ ord(hash[i+16]) ^ ord(deezer.utils.BLOWFISH_SECRET[i]))

    response = urlopen(url)
    cipher = blowfish.Cipher(key.encode())
    length = int(response.getheader('Content-Length'))
    i = 0
    while True:
      chunk = response.read(2048)
      if progress_callback != None:
        if (i * 2048) > length:
          progress_callback(1)
        else:
          progress_callback((i * 2048) / length)
      if not chunk:
        break
      if (i % 3) == 0 and len(chunk) == 2048:
        chunk = b"".join(
          cipher.decrypt_cbc(chunk, b"\x00\x01\x02\x03\x04\x05\x06\x07")
        )
      stream.write(chunk)
      i += 1

  def download(self,
               to_file = None,
               quality = None,
               print_progress = False):
    if quality == None:
      quality = self.guess_quality()
    super().download(
      file_format = deezer.track_quality.DeezerTrackQuality.get_file_format(quality),
      to_file = to_file,
      quality = quality,
      print_progress = print_progress
    )

